import { Member } from '@app/db/common/entity/Member';
import { Injectable } from '@nestjs/common';
import { createHash, randomUUID } from 'crypto';
import { DbService } from '../../../db/db.service';
import { FindOptionsSelect, FindOptionsWhere, In } from 'typeorm';
import { Menu } from '@app/db/common/entity/Menu';
import CreateDto from './dto/create.dto';
import { CacheService } from '@app/cache';
import * as dayjs from 'dayjs';
import { ConfigureService } from '@app/configure';
import UpdateDto from './dto/update.dto';
import PageDto from './dto/page.dto';
import { Role } from '@app/db/common/entity/Role';
import WorkException from 'apps/admin/src/exception/work.exception';
import UpdateDtoInfo from './dto/UpdateDtoInfo.dto';

@Injectable()
export class MemberService {
    constructor(
        private readonly db:DbService,
        private readonly cache:CacheService,
		private readonly config:ConfigureService
    ){}

    generateSalt() {
        return randomUUID().split('-').pop().toLocaleLowerCase()
    }

    buildPassword(password:string, salt:string) {
        password = createHash('sha256').update(password + salt).digest('hex').toLowerCase()
        return createHash('sha256').update(password + salt).digest('hex').toLowerCase()
    }

    async verify(account:string, password:string, merchantId:number = 0) : Promise<Member>{
        
        const member = await this.db.member.findOne({account : account, merchantId : merchantId}, ['name', 'password', 'id', 'salt', 'belong', 'canSuper', 'enable'])
        console.log(member)
        if(!member || this.buildPassword(password, member.salt) != member.password) {
            return null
        }

        return member
    }


    async allMenu(memberId:number) {
        return this.permissionMenu(memberId, {id : true, name : true, parentId : true})
    }


    async permissionMenu(memberId:number, fields:FindOptionsSelect<Menu> = null) {
        const member = await this.db.member.findOne({id : memberId}, ['roles', 'canSuper'])

        if(!member.canSuper && (!member.roles || member.roles.length == 0)) return null


        const where:FindOptionsWhere<Menu> = {

        }

        if(!member.canSuper) {
            const roles = await this.db.role.find({id : In(member.roles)}, ['menus', 'id', 'enable'])
            const menuIds:number[] = []
            roles.forEach(role => {
                if(!role.enable) return

                if(role.menus != null && role.menus.length > 0) {
                    role.menus.forEach(id => {
                        if(!menuIds.includes(id)) {
                            menuIds.push(id)
                        }
                    })
                }

            })

            if(menuIds.length == 0) return null

            where.id = In(menuIds)
        }

        return this.db.menu.find(where, fields, null, null, {sort : 'DESC', id : 'DESC'})
    }


    async page(dto:PageDto, merchantId:number, memberId:number) {
        let result:PaginationResult<Member & {roleNames?:string[]}> = {
            total : 0,
            rows : []
        }

        let where:FindOptionsWhere<Member> = {
            creatorId : memberId,
            merchantId : merchantId
        }

        if(dto.name) {
            where.name = dto.name
        }

        if(dto.account) {
            where.account = dto.account
        }

        result.total = await this.db.member.count(where)

        if(result.total > 0) {




            result.rows = await this.db.member.pagination(dto.pageNo, dto.pageSize, where, {id:true, account:true, name:true, roles:true, tel:true, remark:true, enable:true, canSuper:true}, {id : 'DESC'})

            if(result.rows.length > 0) {
                
                const roleIds:number[] = []
                result.rows.forEach(row => {
                   
                    const roles = row.roles
                    if(roles) {
                        roles.forEach(role => {
                            if(roleIds.includes(role) == false) {
                                roleIds.push(role)
                            }
                        })
                    }
                })

               
                let roles:Role[] = []

                if(roleIds.length > 0) {
                    roles = await this.db.role.find({id : In(roleIds)}, ['name', 'id'])
                }


                if(roles.length > 0) {
                    result.rows.forEach(row => {
                        
                        const userRoles = row.roles as number[]
                        if(userRoles && userRoles.length > 0) {
                            row.roleNames = []
                            for(let i = 0; i < roles.length; i ++) {
                                if(userRoles.includes(roles[i].id)) {
                                    row.roleNames.push(roles[i].name)
                                }
                            }
                        }

                        
                    })
                }
            }
        }

        return result
    }


    async add(dto:CreateDto, merchantId:number, belong:number, memberId:number) {
        const lockKey = 'c:m:' + merchantId + ':' + belong + ':' + dto.account
        const lock = await this.cache.set(lockKey, '1', 10, 'NX')
        if(!lock || (await this.db.member.isExistsByWhere({merchantId, account : dto.account}))) throw new WorkException('已存在相同的账号')
    

		const member:KeyToValueType<Member> = {
			creatorId : memberId,
			belong : belong,
			merchantId : merchantId,
			createTime : dayjs().unix(),
			...dto
		}

        member.salt = this.generateSalt()
        member.password = this.buildPassword(member.password || this.config.get('memberDefaultPassword'), member.salt)

        return this.db.member.insert(member)
    }

    async update(dto:UpdateDto, memberId:number) {
        let fields = Object.keys(dto) as (keyof Member)[]
        if(fields.length == 0) return true


        const row = await this.db.member.findOne({ id : dto.id })
        
        if(!row || row.canSuper) return false

        let updateData:KeyToValueType<Member> = {}

        for(let k in row) {
            if(k in dto) {
                if(k == 'roles') {
                    if(row[k] && dto[k]) {
                        const m1 = [...row[k]]
                        const m2 = [...dto[k]]
                        m1.sort()
                        m2.sort()
                        if(JSON.stringify(m1) == JSON.stringify(m2)) continue
                    }
                }else {
					if(row[k] == dto[k]) continue
				}
                updateData[k] = dto[k]
            }
            
        }
        
        if(Object.keys(updateData).length == 0) return true

        if(updateData.account && (true == await this.db.member.isExistsByWhere({account : updateData.account, merchantId : row.merchantId}))) throw new WorkException('已存在相同的账号')

		if(updateData.password) {
			updateData.salt = this.generateSalt()
			updateData.password = this.buildPassword(updateData.password, updateData.salt)
		}

		updateData.updateTime = dayjs().unix()

        return this.db.member.update({id : dto.id}, updateData)
    }

    async del(id:number) {
        let row = await this.db.member.findOne({ id }, ['id', 'canSuper'])
        if(!row || row.canSuper) return false
        return this.db.member.delete({id : id})
    }


    async resetPwd(memberId:number, password:string) {
        const member = await this.db.member.findOne({id : memberId}, {password : true, salt : true})
        if(this.buildPassword(password, member.salt) == member.password) throw new WorkException('密码不能和旧密码一样')
        member.salt = this.generateSalt()
        member.password = this.buildPassword(password, member.salt)
        
        return this.db.member.update({id : memberId}, member)
    }

    async changePwd(memberId:number, oldpwd:string, newpwd:string) {
        const member = await this.db.member.findOne({id : memberId}, ['password', 'salt'])
        if(this.buildPassword(oldpwd, member.salt) != member.password) throw new WorkException('旧密码错误')
        if(newpwd == oldpwd) throw new WorkException('新密码不能和旧密码一样')

        member.salt = this.generateSalt()
        member.password = this.buildPassword(newpwd, member.salt)
        
        return this.db.member.update({id : memberId}, member)
        
    }


    async updateInfo(memberId:number, dto:UpdateDtoInfo) {
        if(Object.keys(dto).length > 0) {
            return this.db.member.update({id : memberId}, dto)
        }

        return true
    }
}
